#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Filename: 04_stochastic_gradient_descent_1.py
# https://www.2cto.com/net/201610/557111.html
# SGD（Stochastic Gradient Descent）随机梯度下降法

import numpy as np

# 样本总数量
sample_count = 100
# 最大的迭代次数
max_itertion_times = 500

# 用来计算步长的参数
p_1, p_2 = 5, 50


# 使得梯度下降的步长逐渐减小, 使得越接近底部的时候步长越小, 而越小就越容易进阈值(当然, 这里没有设置阈值)
# 随着迭代次数的不断增多，学习率不变，梯度的绝对值是不断变小的，所以步长也会随着不断变小
def learning_schedule(p):
    return p_1 / (p + p_2)


# 使用1填充一个 100 * 1 的矩阵
X_0 = np.ones((sample_count, 1))
# 创建一个 100 * 1 的矩阵, 每个值都乘以2
X_1 = 2 * np.random.rand(sample_count, 1)
# 给写好的方程 加上一个标准正态分布上的随机取值
y = 4 + 3 * X_1 + np.random.randn(sample_count, 1)
# 把前面生成的两个矩阵合二为一, 0代表第0列, 1代表第1列
X = np.c_[X_0, X_1]

# 随机初始化一个theta, 从这个值上开始迭代
theta = np.random.randn(2, 1)

# 控制总的最大迭代次数
for epoch in range(max_itertion_times):
    # 控制在每次迭代中都跑样本个数次,每个样本次都获取部分数据来计算梯度值
    for index in range(sample_count):
        random_int = np.random.randint(sample_count)
        xi = X[random_int:random_int + 1]
        yi = y[random_int:random_int + 1]
        gradients = 2 * xi.T.dot(xi.dot(theta) - yi)
        # 可以在这里给gradients设置阈值, 一旦进入阈值范围, 直接跳出循环
        learning_rate = learning_schedule(epoch * sample_count + index)
        theta = theta - learning_rate * gradients
        
print(theta)
